/*****************************************************************************/
/*                                                                           */
/*    Domino Operation System System Specific Module                         */
/*                                                                           */
/*    Copyright (C) 2010 Laszlo Arvai                                        */
/*                                                                           */
/*    ------------------------------------------------------------------     */
/*    netSocket - Network socket API implementation                          */
/*****************************************************************************/

/*****************************************************************************/
/* Includes                                                                  */
/*****************************************************************************/
#include <netSocket.h>
#include <netPacketBuffer.h>
#include <netICMP.h>
#include <netUDP.h>
#include <netMAC.h>
#include <netPPP.h>
#include "sysSettings.h"

/*****************************************************************************/
/* Global variables                                                          */
/*****************************************************************************/
netSocketInfo g_netSockets[netMAX_SOCKET];

/*****************************************************************************/
/* Module global variables                                                   */
/*****************************************************************************/
static WORD g_auto_port_number = 1024;

/*****************************************************************************/
/* Socket Functions                                                          */
/*****************************************************************************/

///////////////////////////////////////////////////////////////////////////////
//! This function creates a new Berkeley socket.
//! This function creates a new BSD socket for the TCPIP stack. 
//! The return socket descriptor is used for the subsequent BSD operations.
//! \param address family - AF_INET
//! \param socket type SOCK_DGRAM or SOCK_STREAM
//! \param IP protocol IPPROTO_UDP or IPPROTO_TCP.
//! \return New socket descriptor. INVALID_SOCKET in case of error.
netSOCKET netSocketOpen( int af, int type, int protocol )
{
  netSocketInfo* socket = g_netSockets;
  netSOCKET s;

  if( af != AF_INET )
      return INVALID_SOCKET;

  for( s = 0; s < netMAX_SOCKET; s++,socket++ )
  {
    if( socket->State != netSS_CLOSED ) //socket in use
        continue;

    socket->Type = type;
      
    if( type == SOCK_DGRAM && protocol == IPPROTO_UDP )
    {
        socket->State = netSS_CREATED;
        return s;
    }
    else if( type == SOCK_STREAM && protocol == IPPROTO_TCP )
    {
        socket->State = netSS_CREATED;
        return s;
    }
    else
        return INVALID_SOCKET;
  }

  return INVALID_SOCKET;
}

///////////////////////////////////////////////////////////////////////////////
//! The closesocket function closes an existing socket.  
//! This function releases the socket descriptor s.  
//! Further references to s fails with SOCKET_ERROR code.  
//! Any data buffered at the socket is discarded.  If the 
//! socket s is no longer needed, closesocket() must be 
//! called in order to release all resources associated 
//! with s.
//! \param Socket descriptor returned from a previous call to socket
//! \retval If closesocket is successful, a value of 0 is returned. 
//!         A return value of SOCKET_ERROR (-1) indicates an error.
int netCloseSocket( netSOCKET s )
{
	netSocketInfo* socket;

  // sanity check
  if( s >= netMAX_SOCKET )
    return SOCKET_ERROR;

  socket = &g_netSockets[s];

	if( socket->Type == SOCK_DGRAM )
  {
		// UDP sockets
		if( socket->State != netSS_CLOSED ) //this is actually in use
    {
			// change status
			socket->State = netSS_CLOSED;

			//empty receiver buffer
			while( socket->FirstPendingPacket != netINVALID_CHUNK )
				netDeleteSocketRXPacket( s );

			//success
      return 0;
    }
    else
	    return SOCKET_ERROR;
  }
	else
  {
		//tcp sockets

		// TODO	

		//success
		return 0;
  }
}

///////////////////////////////////////////////////////////////////////////////
//! This function assigns a name to the socket descriptor.
//! The bind function assigns a name to an unnamed socket. The
//! name represents the local address of the communication
//! endpoint. For sockets of type SOCK_STREAM, the name of the
//! remote endpoint is assigned when a connect or accept function
//! is executed.
//! \param Socket descriptor returned from a previous call to socket.
//! \param Pointer to the the sockaddr structure containing the local address of the socket.
//! \param Length of the sockaddr structure.
//! \return If bind is successful, a value of 0 is returned. A return value of SOCKET_ERROR indicates an error.
int netBind( netSOCKET s, const struct sockaddr* name, int namelen )
{
  netSocketInfo* socket;
  struct sockaddr_in *local_addr;
  dosWord local_port;
    
  // sanity check
  if( s >= netMAX_SOCKET )
    return SOCKET_ERROR;

  socket = &g_netSockets[s];

  if( socket->State != netSS_CREATED ) //only work with recently created socket
    return SOCKET_ERROR;

  if( (unsigned int)namelen < sizeof(struct sockaddr_in) )
    return SOCKET_ERROR;

  local_addr = (struct sockaddr_in *)name;

  local_port = local_addr->sin_port;
  if( local_port == 0 ) //pick a port
  {
    local_port = g_auto_port_number++;
    if( g_auto_port_number == 5000) // reset the port numbers
      g_auto_port_number = 1024;
  }

  socket->LocalPort = local_port;
  socket->State     = netSS_BOUND;

  //success  
  return 0; 
}

///////////////////////////////////////////////////////////////////////////////
//! This function connects to the peer communications end point.
//! The connect function assigns the address of the peer
//! communications endpoint. For stream sockets, connection is
//! established between the endpoints. For datagram sockets, an
//! address filter is established between the endpoints until
//! changed with another connect() function.
//! \param Socket descriptor returned from a previous call to socket.
//! \param Pointer to the sockaddr structure containing the peer address and port number.
//! \param Length of the sockaddr structure.
//! \return If the connect() function succeeds, it returns 0. Otherwise, the value SOCKET_ERROR is returned to indicate an error condition. For stream based socket, if the connection is not established yet, connect returns SOCKET_CNXN_IN_PROGRESS.
int netConnect( netSOCKET s, const struct sockaddr* name, int namelen )
{
  netSocketInfo* socket;
	dosDWord remoteIP;
  dosWord remotePort;
  struct sockaddr_in *addr;

	// sanity check
  if( s >= netMAX_SOCKET )
    return SOCKET_ERROR;

  socket = &g_netSockets[s];

  if( socket->State < netSS_CREATED )
		return SOCKET_ERROR;

  if( (unsigned int)namelen < sizeof(struct sockaddr_in))
		return SOCKET_ERROR;

	addr = (struct sockaddr_in *)name;
  remotePort	= addr->sin_port;      
  remoteIP    = addr->sin_addr.S_un.S_addr;

	if( remoteIP == 0 || remotePort == 0 )
		return SOCKET_ERROR;
        
  if( socket->Type == SOCK_STREAM )
  {
  }
  else
  {   
		// UDP: remote port is used as a filter only. Need to call connect when using send/recv
		// calls. no need to call 'connect' if using sendto/recvfrom calls.
    socket->LocalPort         = remotePort;
		socket->RemotePort				= remotePort;
		socket->RemoteAddress.Val = remoteIP;
		socket->State							= netSS_READY;

		// success
		return 0;
  }

	return SOCKET_ERROR;
}

///////////////////////////////////////////////////////////////////////////////
//! This function is used to receive incoming data that has been queued for a socket.
//! This function can be used with both datagram and stream type sockets. If the available data
//! is too large to fit in the supplied application buffer buf, excess bytes are discarded in case of SOCK_DGRAM type
//! sockets. For SOCK_STREAM types, the data is buffered internally so the application can retreive all data by
//! multiple calls of recvfrom.
//! \param Socket descriptor returned from a previous call to socket
//! \param Application data receive buffer
//! \param Buffer length in bytes
//! \param Message flags. Currently this is not supported
//! \param Pointer to the sockaddr structure that will be filled in with the destination address
//! \param size of buffer pointed by from
//! \return If recvfrom is successful, the number of bytes copied to application buffer buf is returned.
//! A value of zero indicates no data available. A return value of SOCKET_ERROR (-1) indicates an error condition.
int netRecvfrom( netSOCKET s, char* buf, int len, int flags, struct sockaddr* from, int* fromlen )
{
  netPacketInfo packet_info;
  int length = SOCKET_ERROR;
  struct sockaddr_in *rem_addr;

  rem_addr = (struct sockaddr_in *)from;

	// sanity check
	if( s >= netMAX_SOCKET )
		return SOCKET_ERROR;

	// check for UDP socket type 
  if( g_netSockets[s].Type == SOCK_DGRAM )
  {
    // check for socket state
    if( g_netSockets[s].State < netSS_BOUND )
      return SOCKET_ERROR;

    // open first packet in the socket queue
    if( netOpenSocketRXPacket( s, &packet_info ) )
    {
      // copy payload
      length = (packet_info.PayloadLength > len) ? len : packet_info.PayloadLength;
      netReadBlockRXPacket( (dosByte*)buf, (dosWord)length );

      if( rem_addr != DOS_NULL )
      {
        rem_addr->sin_addr.S_un.S_addr = packet_info.Address.Val;
        rem_addr->sin_port = packet_info.RemotePort;
      }

      if( fromlen != DOS_NULL )
        *fromlen = sizeof(struct sockaddr_in);

      // delete packet
      netDeleteSocketRXPacket(s);
    }
  }

  return length;
}

///////////////////////////////////////////////////////////////////////////////
//! The send function is used to send outgoing data on an already connected socket.
//! The send function is used to send outgoing data on an already
//! connected socket. This function is used to send a reliable,
//! ordered stream of data bytes on a socket of type SOCK_STREAM
//! but can also be used to send datagrams on a socket of type SOCK_DGRAM.
//! \param Socket descriptor returned from a previous call to socket.
//! \param Application data buffer containing data to transmit.
//! \param Length of data in bytes.
//! \param Message flags. Currently this field is not supported.
//! \return On success, send returns number of bytes sent. In case of error, returns SOCKET_ERROR. a zero indicates no data send.
int netSend( netSOCKET s, const char* buf, int len, int flags )
{
   return netSendto(s, buf, len, flags, DOS_NULL, 0);
}

///////////////////////////////////////////////////////////////////////////////
//! This function used to send the data for both connection oriented and
//! connection-less sockets.
//! The sendto function is used to send outgoing data on a socket.
//! The destination address is given by to and tolen. Both 
//! Datagram and stream sockets are supported.
//! \param Socket descriptor returned from a previous call to socket.
//! \param Application data buffer containing data to transmit.
//! \param Length of data in bytes.
//! \param Message flags. Currently this field is not supported.
//! \param Optional pointer to the the sockaddr structure containing the
//! destination address.  If NULL, the currently bound remote port and IP 
//! address are used as the destination.
//! \param Length of the sockaddr structure.
//! \return On success, sendto returns number of bytes sent. In case of error returns SOCKET_ERROR
int netSendto( netSOCKET s, const char* buf, int len, int flags, const struct sockaddr* to, int tolen )
{
	netSocketInfo* socket;
	int size = SOCKET_ERROR;
	struct in_addr remote_address;
	dosWord remote_port;
	struct sockaddr_in local;
	dosBool success;
	netIPHeaderInfo ip_header;
	dosWord protocol_header_pos;

	// sanity check
	if( s >= netMAX_SOCKET )
		return SOCKET_ERROR;

	socket = &g_netSockets[s];

	if( socket->State == netSS_CLOSED )
		return SOCKET_ERROR;

	if(socket->Type == SOCK_DGRAM) //UDP
	{
		// Decide the destination IP address and port
		remote_address = socket->RemoteAddress;
		remote_port = socket->RemotePort;
		if(to)
		{
			if( (unsigned int)tolen != sizeof(struct sockaddr_in) )
				return SOCKET_ERROR;

			 remote_port = ((struct sockaddr_in*)to)->sin_port;
			 remote_address.Val = ((struct sockaddr_in*)to)->sin_addr.Val;
	     
			 // Implicitly bind the socket if it isn't already
			 if( socket->State == netSS_CREATED )
			 {
				 local.sin_port = 0;
				 if( netBind(s, (struct sockaddr*)&local, sizeof(local)) == SOCKET_ERROR)
						 return SOCKET_ERROR;
			 }
		}

		if( remote_address.Val == IP_ADDR_ANY)
			 remote_address.Val = 0xFFFFFFFFu;

		// Set the remote IP and MAC address if it is different from what we already have stored in the UDP socket
		if( socket->RemoteAddress.Val != remote_address.Val)
		{
			/*
			 if(ARPIsResolved(&remoteInfo.IPAddr, &remoteInfo.MACAddr))
			 {
					memcpy((void*)&UDPSocketInfo[socket->SocketID].remoteNode, (void*)&remoteInfo, sizeof(remoteInfo));
			 }
			 else
			 {
					if(TickGet() - startTick > 1*TICK_SECOND)
					{
						 ARPResolve(&remoteInfo.IPAddr);
						 startTick = TickGet();
					}
					return SOCKET_ERROR;
			 }*/
		}
	  
		// create UDP packet
		success = netCreateTXPacket(netPT_IP);
		if( success )
		{
			ip_header.Address = remote_address;
			ip_header.HeaderLength = netIP_HEADER_LENGTH;
			ip_header.PacketLength = netIP_HEADER_LENGTH + netUDP_HEADER_LENGTH + len;
			ip_header.Protocol = netIPDT_UDP;
			netIPCreateHeader( &ip_header );
	
			protocol_header_pos = netGetTXPacketPos();

      success = netCreateUDPHeader( socket->LocalPort, remote_port, len );
		}

    // write data
    if( success )
      success = netWriteBlockTXPacket( (dosByte*)buf, len );

    // send packet
		if( success )
		{
      netSetTXPacketPos( protocol_header_pos );
      netUpdateUDPHeader( &ip_header, len);
			netSendTXPacket();
			return len;
		}
		else
		{
			netCancelTXPacket();
			return SOCKET_ERROR;
		}
	}
	else if(socket->Type == SOCK_STREAM) //TCP will only send to the already established socket.
	{
	}

	return SOCKET_ERROR;
}

/*****************************************************************************/
/* General Functions                                                         */
/*****************************************************************************/

///////////////////////////////////////////////////////////////////////////////
//! Initializes all network data structures and modules
void netInitialize(void)
{
  int s;
  netSocketInfo* pSocket;

  // init socket data structure
  for ( s = 0; s < netMAX_SOCKET; s++ )
  {
    pSocket                     = (netSocketInfo*)&g_netSockets[s];
    pSocket->Type               = 0;
    pSocket->State              = netSS_CLOSED;
    pSocket->LocalPort          = 0;
    pSocket->FirstPendingPacket = netINVALID_CHUNK;
  }
  
  // init modules
  netBufferInitialize();
  netMACInitialize();
  netIPInitialize();
}

///////////////////////////////////////////////////////////////////////////////
//! Finds the specified socket
//! \param Type of the socket (SOCK_DGRAM, SOCK_STREAM)
//! \param Local port number of the socket
//! \param Socket found or INVALID_SOCKET if there is no such a socket
netSOCKET netFindSocket( dosByte in_type, dosWord in_local_port )
{
  int s;

  // find socket
  for ( s = 0; s < netMAX_SOCKET; s++ )
  {
    if( g_netSockets[s].State == netSS_BOUND )
    {
      if( g_netSockets[s].Type == in_type && g_netSockets[s].LocalPort == in_local_port )
      {
        return (netSOCKET)s;
      }
    }
  }
  
  return INVALID_SOCKET;
}

///////////////////////////////////////////////////////////////////////////////
//! Processes packages (must be called periodically for proper operation)
//! \return True if there are more operation pending
dosBool netProcess(void)
{
	dosBool operation_pending = dosFalse;
	dosBool packet_type;

// Periodic connection function if client mode enabled
#ifdef netPPP_CLIENT_MODE
	netPPPProcess();
#endif

	// check for received packages
	if( netIsRXPacketAvailable() )
	{
		// process packet type
		packet_type = netGetAvailablePacketType();
		switch( packet_type )
		{
			// process IP packet
			case netPT_IP:
				netIPProcess();
				break;

#ifdef netPPP_ENABLED
      case netPT_LCP:
        netLCPProcess();
        break;
  
      case netPT_IPCP:
        netIPCPProcess();
        break;
        
      case netPT_PAP:
//        break;
      
      case netPT_CHAP:
//        break;

      case netPT_UknownPPP:
				netPPPUnknownProcess();
				break;
#endif

			// unknown packet -> delete it
			default:
				netDeleteRXPacket();
				break;
		}
	
		// if more packets are pending set the flag
		if( netIsRXPacketAvailable() )
			operation_pending = dosTrue;
	}
	
	return operation_pending;
}
